Chapter 7: Orquestração e O Teste de Fumaça Final
O Arquiteto moveu o cursor para consolidar a injeção das callbacks. Ele revisou a estrutura do UIManager, agora purificado: a UI apenas apresentava dados e delegava ações; não mais se intrometia diretamente nos módulos de persistência ou lógica. O acoplamento rígido havia sido removido dos itens individuais, concentrado agora na rotina de renderização.
No entanto, havia uma interdependência oculta que precisava ser formalizada. O TeleportModule.Teleport era o coração do movimento, e o UIManager (via SetMethodVisual) era quem determinava se o sistema estava no modo instant ou tween, salvando essa decisão no DataModule.
Para a abstração ser completa, o TeleportModule deveria ser autoconsciente. Ele não podia depender de que o UIManager passasse a configuração; ele precisava ativamente buscar o estado atual do sistema para tomar a decisão correta de execução.
A Formalização da Persistência de Configurações
O DataModule já possuía a estrutura para armazenar configurações, mas o Arquiteto decidiu introduzir um pequeno boilerplate para aumentar a legibilidade e evitar o uso de strings mágicas ("teleportMethod", "tweenSpeed") espalhadas pelo código.
No DataModule (ou em um módulo de Constantes acessível a todos), ele definiu:
-- Constants.lua (Novo arquivo ou extensão do DataModule) local SettingsKeys = { TeleportMethod = "teleportMethod", TweenSpeed = "tweenSpeed", -- Futuras configurações de UX/Funcionalidade ShowTutorial = "showTutorial", UIScale = "uiScale" } return SettingsKeys
Com esta pequena alteração, qualquer módulo que precisasse interagir com as configurações persistidas usaria agora SettingsKeys.TeleportMethod, em vez da string bruta.
Agora, o foco retornou ao TeleportModule.
O Orquestrador Lógico: TeleportModule
Anteriormente, o TeleportModule.Teleport(cframeData) era um stub que assumia a lógica interna, mas o Arquiteto precisava garantir que essa lógica fosse robusta e orientada ao estado.
O fluxo de decisão dentro de TeleportModule.Teleport deveria ser o seguinte:
- Validar o cframeData e o estado do personagem (vivo, HRP presente). Se falhar, retornar false e a razão.
- Importar o DataModule (já deveria estar feito na inicialização do módulo).
- Ler a configuração do método de teleporte (usando o SettingsKeys recém-definido).
- Se o método for instant, chamar TeleportInstant.
- Se o método for tween, ler a velocidade de Tween do DataModule e chamar TeleportTween(cframeData, velocidade).
- Gerenciar o estado IsTeleporting para evitar teleporte simultâneo.
O Arquiteto abriu o arquivo TeleportModule.lua.
-- TeleportModule.lua local DataModule = require(script.Parent.DataModule) local SettingsKeys = require(script.Parent.Constants).SettingsKeys -- Assumindo Constants no mesmo nível local RunService = game:GetService("RunService") local Players = game:GetService("Players") -- Estado interno do módulo local isCurrentlyTeleporting = false local tweenCancelConnection = nil -- ... (Outras funções auxiliares: ValidateHRP, CFrameToVector3, TeleportInstant, TeleportTween) ... -- Função de validação expandida local function ValidatePreTeleport(cframeData) if isCurrentlyTeleporting then return false, "Teleporte já em curso. Aguarde." end local player = Players.LocalPlayer if not player or not player.Character then return false, "Personagem não carregado." end local hrp = ValidateHRP(player.Character) if not hrp then return false, "HumanoidRootPart ausente ou inválido." end -- Aqui poderia haver lógica de validação de 'sub-mapa válida'. -- Por enquanto, a presunção é que se o HRP existe, o teleporte pode ocorrer. return true, hrp end --- -- Função Principal: Orquestra o Teleporte --- function TeleportModule.Teleport(cframeData) local success, resultOrHRP = ValidatePreTeleport(cframeData) if not success then return false, resultOrHRP -- Retorna erro de validação (ex: "Teleporte já em curso") end local hrp = resultOrHRP local targetPosition = DataModule.CFrameToVector3(cframeData) -- 1. Leitura Dinâmica do Método Salvo local method = DataModule.GetSetting(SettingsKeys.TeleportMethod, "instant") local finished if method == "tween" then -- 2. Leitura da Velocidade de Tween Salva local speed = DataModule.GetSetting(SettingsKeys.TweenSpeed, 50) -- Garante que a velocidade seja um valor numérico válido (Defensive Programming) if type(speed) ~= "number" or speed <= 0 then warn("[TeleportModule] Velocidade de Tween inválida. Usando default (50).") speed = 50 end -- Chama a função Tween, que gerencia isCurrentlyTeleporting finished = TeleportModule.TeleportTween(hrp, targetPosition, speed) else -- Default (incluindo "instant" e qualquer valor inesperado) TeleportModule.TeleportInstant(hrp, targetPosition) finished = true -- Teleporte instantâneo é sucesso imediato end -- O retorno de 'finished' reflete se a *tentativa* de teleporte foi iniciada/concluída sem falhas. -- Para teleporte instantâneo, é sempre true se o HRP estava lá. -- Para teleporte Tween, é true se o Tween iniciou corretamente. return finished end
A alteração chave era que a função Teleport agora olhava ativamente para dentro do DataModule para buscar as preferências do usuário, em vez de depender de argumentos externos. Isso tornou o TeleportModule independente da UI na tomada de decisão sobre o modo de viagem, apenas dependendo do DataModule (o hub de estado) e do destino (o CFrameData passado).
O Ajuste Fino da Função Tween
O Arquiteto percebeu que precisava garantir que a função TeleportTween fosse robusta o suficiente para receber a velocidade calculada e gerenciar o estado isCurrentlyTeleporting adequadamente.
A implementação de TeleportTween já devia existir desde o Capítulo 3, mas precisava ser confirmada a gestão de estado:
-- TeleportModule.lua (Revisão da lógica de TeleportTween) function TeleportModule.TeleportTween(hrp, targetPosition, speed) local distance = (hrp.Position - targetPosition).Magnitude if distance < 1 then return true -- Já no local end -- O tempo é a distância dividido pela velocidade: Time = Distance / Speed local time = distance / speed local tweenService = game:GetService("TweenService") -- Cria a informação do Tween local tweenInfo = TweenInfo.new( time, -- Duração calculada Enum.EasingStyle.Linear, -- Easing simples Enum.EasingDirection.Out, 0, -- Repetições false, -- Reversa 0 -- Atraso ) local goal = {CFrame = CFrame.new(targetPosition)} isCurrentlyTeleporting = true -- Marca o início do teleporte local tween = tweenService:Create(hrp, tweenInfo, goal) tweenCancelConnection = hrp.AncestryChanged:Connect(function() if not hrp.Parent and isCurrentlyTeleporting then -- Personagem morreu, o HRP foi destruído/reparentado tween:Cancel() isCurrentlyTeleporting = false tweenCancelConnection:Disconnect() tweenCancelConnection = nil warn("[TeleportModule] Teleporte Tween cancelado: Personagem destruído.") end end) local finishedTween = false tween.Completed:Connect(function(playbackState) if playbackState == Enum.PlaybackState.Completed then finishedTween = true end isCurrentlyTeleporting = false -- Marca o fim do teleporte if tweenCancelConnection then tweenCancelConnection:Disconnect() tweenCancelConnection = nil end end) tween:Play() -- Espera o Tween terminar (ou ser cancelado) -- Em ambiente de executor, a função pode não precisar esperar o Completed para retornar sucesso, -- mas precisa garantir que o estado 'isCurrentlyTeleporting' seja mantido até lá. -- Para fins de feedback visual imediato na UI (sucesso = true), retornamos true se o Tween iniciou. return true end
Ao garantir que o TeleportModule gerencia internamente o estado de teleporte ativo (isCurrentlyTeleporting) e a leitura das configurações, o Arquiteto solidificou a camada de lógica, separando-a completamente da UI, exceto pelo feedback visual de sucesso/falha pós-clique.
Preparação do Teste de Fumaça (Smoke Test)
Com a injeção de dependência na UI e o orquestrador no TeleportModule prontos, a última etapa era realizar um teste prático. O objetivo era simples: confirmar que a UI salva a configuração e o TeleportModule a lê corretamente em tempo real, sem falhas de sincronização.
O Arquiteto inicializou o sistema no ambiente de teste, injetando o script. A UI flutuava no canto superior esquerdo.
Cenário 1: Testando o modo Instantâneo
- Estado Inicial: O DataModule é carregado. Por padrão, ou pelo último save, o sistema está configurado para instant. A UI reflete isso visualmente (botão "Instantâneo" destacado, controle de velocidade oculto).
- Criação de Waypoint de Teste: O Arquiteto posiciona o personagem em uma área segura. Utiliza a caixa de input, digita "Cian Teste 1" e clica em Criar.
- Console: [UIManager] Lista renderizada após refatoração delegado. [1] Waypoints encontrados.
- Internamente: DataModule.AddWaypoint salvou o estado e disparou o evento WaypointsChanged.
- Execução do Teleporte: O Arquiteto move o personagem para longe do ponto inicial. Clica no botão de teleporte ("TP") no item "Cian Teste 1".
O que deve acontecer no console:
- Iniciando teleporte delegado para: Cian Teste 1
- (Oculto no TeleportModule): DataModule.GetSetting("teleportMethod") retorna "instant".
- (Oculto no TeleportModule): TeleportModule.TeleportInstant é chamado.
- Resultado: O personagem teleporta instantaneamente. O botão de TP pisca verde para sucesso.
O teste instantâneo funcionou exatamente como previsto. O acoplamento indireto via DataModule estava funcional.
Cenário 2: Testando o modo Tween e a Sincronização
O ponto crucial era garantir que a mudança na UI se propagasse para o TeleportModule via DataModule.
- Mudança de Método: O Arquiteto clica no botão "Tween" na área de controle da UI.
- A UI responde: botão "Tween" é destacado, a altura da ControlArea se expande, revelando o SpeedInput (atualizado para, digamos, 75).
- Internamente: SetMethodVisual("tween") é chamado. Este, por sua vez, executa DataModule.SetSetting(SettingsKeys.TeleportMethod, "tween").
- Console: [DataModule] Configuração 'teleportMethod' salva: tween.
- Ajuste de Velocidade: O Arquiteto clica no SpeedInput e altera o valor de 75 para 150 (studs/s).
- Internamente: O SpeedInput.FocusLost dispara a validação e, em seguida, DataModule.SetSetting(SettingsKeys.TweenSpeed, 150).
- Console: [DataModule] Configuração 'tweenSpeed' salva: 150.
- Movimentação e Execução do Teleporte: O Arquiteto move o personagem para uma nova posição. Clica no botão de teleporte ("TP") no item "Cian Teste 1".
O que deve acontecer no console e no jogo:
- Iniciando teleporte delegado para: Cian Teste 1
- (Oculto no TeleportModule): DataModule.GetSetting("teleportMethod") retorna "tween".
- (Oculto no TeleportModule): DataModule.GetSetting("tweenSpeed") retorna 150.
- (Oculto no TeleportModule): TeleportModule.TeleportTween(..., 150) é chamado.
- Resultado: O personagem deve se mover suavemente em direção ao waypoint, e o movimento deve ser perceptivelmente mais rápido do que se estivesse na velocidade padrão (50 ou 75). O botão de TP pisca verde.
O Arquiteto observa o avatar deslizar rapidamente os 150 studs em direção ao ponto de teste. A velocidade acelerada confirma que o TeleportModule leu o valor 150 do DataModule.
O sistema estava totalmente operacional e modular.
Consistência de Estado e Reversibilidade
Um elemento que o Arquiteto precisava confirmar era a reversibilidade imediata.
- Reversão Imediata: Enquanto o personagem estava se movendo em Tween, ele clicou no botão "Instantâneo" no MethodSelect da UI.
- Internamente: SetMethodVisual("instant") é chamado, salvando "instant" no DataModule.
- Novo Teleporte: Uma vez que o Tween atual termina (isCurrentlyTeleporting se torna false), o Arquiteto clica novamente no TP.
- O TeleportModule lê e executa a nova configuração: "instant".
- O teleporte ocorre instantaneamente.
A UI e a lógica de execução estavam perfeitamente sincronizadas através do DataModule, que atuava como uma fonte única de verdade para as configurações.
Considerações Finais de Arquitetura
O Arquiteto contemplou o código finalizado.
Encapsulamento do DataModule: O DataModule geria a complexidade de writefile/readfile e a serialização de CFrames, expondo apenas funções limpas (AddWaypoint, GetSetting).
UI Pura: O UIManager estava concentrado em apresentação e em delegar; ele não continha lógica complexa de movimento ou persistência.
Orquestrador (TeleportModule): O módulo de teleporte era agora um orquestrador consciente, capaz de buscar suas próprias diretrizes de execução (instant vs. tween, speed) sem depender do chamador (a UI) para injetar essas informações. Isso o tornava mais robusto; se, no futuro, o TeleportModule fosse chamado por outro subsistema (e.g., um módulo de rotas automáticas), ele ainda saberia a preferência do usuário.
A arquitetura estava limpa, orientada a eventos (para a lista de waypoints via WaypointsChanged) e orientada a estado persistido (para configurações via GetSetting). O risco de regressão era mínimo, e a capacidade de manutenção seria alta.
O Arquiteto moveu-se para a rotina de inicialização do sistema, garantindo que o DataModule fosse carregado a tempo, antes de qualquer interação da UI ou do TeleportModule com as configurações.
-- SystemCore.lua (O script principal que coordena todos os módulos) -- ... Inicialização de Modules local DataModule = require(Modules.DataModule) local TeleportModule = require(Modules.TeleportModule) local UIManager = require(Modules.UIManager) -- Passo 1: Inicializar Persistência -- Garante que o estado (waypoints e settings) seja lido do disco ao iniciar. DataModule.Initialize() -- Passo 2: Inicializar a UI -- A UI lê as settings carregadas pelo DataModule.Initialize() -- e liga as ações (TeleportAction, DeleteAction) com os módulos reais. UIManager.Initialize() -- ... (Outras rotinas, como a do sub-mapa listener)
O fluxo de inicialização garantido: carregar dados primeiro, depois construir a UI em cima do estado carregado. Perfeito.
O Arquiteto confirmou que, durante o teste de fumaça, alternar o método na UI e executar um teleporte observou no console a confirmação de que o TeleportModule leu a configuração correta do DataModule subjacente. Esse era o ponto de validação final para esta etapa. A máquina estava selada.
Comments (0)
No comments yet. Be the first to share your thoughts!